// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using ILLink.Shared.DataFlow;
using ILLink.Shared.TypeSystemProxy;
using MultiValue = ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>;

// This is needed due to NativeAOT which doesn't enable nullable globally yet
#nullable enable

namespace ILLink.Shared.TrimAnalysis
{
    [StructLayout(LayoutKind.Auto)] // A good way to avoid CS0282, we don't really care about field order
    internal partial struct HandleCallAction
    {
        private static ValueSetLattice<SingleValue> MultiValueLattice => default;

        private readonly DiagnosticContext _diagnosticContext;
        private readonly FlowAnnotations _annotations;
        private readonly RequireDynamicallyAccessedMembersAction _requireDynamicallyAccessedMembersAction;
        private readonly bool _isNewObj;

        public bool Invoke(MethodProxy calledMethod, MultiValue instanceValue, IReadOnlyList<MultiValue> argumentValues, IntrinsicId intrinsicId, out MultiValue methodReturnValue)
        {
            MultiValue? maybeMethodReturnValue;

            var handledIntrinsic =
                TryHandleIntrinsic(calledMethod, instanceValue, argumentValues, intrinsicId, out maybeMethodReturnValue) ||
                TryHandleSharedIntrinsic(calledMethod, instanceValue, argumentValues, intrinsicId, out maybeMethodReturnValue);

            // As a convenience, if the code above didn't set the return value (and the method has a return value),
            // we will set it to be an unknown value with the return type of the method.
            var annotatedMethodReturnValue = _annotations.GetMethodReturnValue(calledMethod, _isNewObj);
            bool returnsVoid = calledMethod.ReturnsVoid();
            methodReturnValue = maybeMethodReturnValue ??
                ((returnsVoid && !_isNewObj)
                    ? MultiValueLattice.Top
                    : annotatedMethodReturnValue);

            // Validate that the return value has the correct annotations as per the method return value annotations
            if (annotatedMethodReturnValue.DynamicallyAccessedMemberTypes != 0)
            {
                foreach (var uniqueValue in methodReturnValue.AsEnumerable())
                {
                    if (uniqueValue is ValueWithDynamicallyAccessedMembers methodReturnValueWithMemberTypes)
                    {
                        if (!methodReturnValueWithMemberTypes.DynamicallyAccessedMemberTypes.HasFlag(annotatedMethodReturnValue.DynamicallyAccessedMemberTypes))
                            throw new InvalidOperationException($"Internal trimming error: processing of call from {GetContainingSymbolDisplayName()} to {calledMethod.GetDisplayName()} returned value which is not correctly annotated with the expected dynamic member access kinds.");
                    }
                    else if (uniqueValue is SystemTypeValue)
                    {
                        // SystemTypeValue can fulfill any requirement, so it's always valid
                        // The requirements will be applied at the point where it's consumed (passed as a method parameter, set as field value, returned from the method)
                    }
                    else if (uniqueValue == NullValue.Instance)
                    {
                        // NullValue can fulfill any requirements because reflection access to it will typically throw.
                    }
                    else
                    {
                        throw new InvalidOperationException($"Internal trimming error: processing of call from {GetContainingSymbolDisplayName()} to {calledMethod.GetDisplayName()} returned value which is not correctly annotated with the expected dynamic member access kinds.");
                    }
                }
            }

            return handledIntrinsic;
        }

        private partial bool TryHandleIntrinsic(
            MethodProxy calledMethod,
            MultiValue instanceValue,
            IReadOnlyList<MultiValue> argumentValues,
            IntrinsicId intrinsicId,
            out MultiValue? methodReturnValue);

        bool TryHandleSharedIntrinsic(
            MethodProxy calledMethod,
            MultiValue instanceValue,
            IReadOnlyList<MultiValue> argumentValues,
            IntrinsicId intrinsicId,
            out MultiValue? methodReturnValue)
        {
            MultiValue? returnValue = methodReturnValue = null;

            bool requiresDataFlowAnalysis = _annotations.MethodRequiresDataFlowAnalysis(calledMethod);
            var annotatedMethodReturnValue = _annotations.GetMethodReturnValue(calledMethod, _isNewObj);
            Debug.Assert(requiresDataFlowAnalysis || annotatedMethodReturnValue.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.None);

            switch (intrinsicId)
            {
                case IntrinsicId.IntrospectionExtensions_GetTypeInfo:
                    Debug.Assert(instanceValue.IsEmpty());
                    Debug.Assert(argumentValues.Count == 1);

                    // typeof(Foo).GetTypeInfo()... will be commonly present in code targeting
                    // the dead-end reflection refactoring. The call doesn't do anything and we
                    // don't want to lose the annotation.
                    returnValue = argumentValues[0];
                    break;

                case IntrinsicId.TypeInfo_AsType:
                    // someType.AsType()... will be commonly present in code targeting
                    // the dead-end reflection refactoring. The call doesn't do anything and we
                    // don't want to lose the annotation.
                    returnValue = instanceValue;
                    break;

                //
                // UnderlyingSystemType
                //
                case IntrinsicId.Type_get_UnderlyingSystemType:
                    // This is identity for the purposes of the analysis.
                    returnValue = instanceValue;
                    break;

                case IntrinsicId.Type_GetTypeFromHandle:
                    // Infrastructure piece to support "typeof(Foo)" in IL and direct calls everywhere
                    if (argumentValues[0].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    foreach (var value in argumentValues[0].AsEnumerable())
                    {
                        AddReturnValue(value switch
                        {
                            RuntimeTypeHandleForNullableSystemTypeValue nullableSystemType
                                => new NullableSystemTypeValue(nullableSystemType.NullableType, nullableSystemType.UnderlyingTypeValue),
                            // When generating type handles from IL, the GenericParameterValue with DAM annotations is not available.
                            // Once we convert it to a Value with annotations here, there is no need to convert it back in get_TypeHandle
                            RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers nullableDamType when nullableDamType.UnderlyingTypeValue is RuntimeTypeHandleForGenericParameterValue underlyingGenericParameter
                                => new NullableValueWithDynamicallyAccessedMembers(nullableDamType.NullableType, _annotations.GetGenericParameterValue(underlyingGenericParameter.GenericParameter)),
                            // This should only happen if the code does something like typeof(Nullable<>).MakeGenericType(methodParameter).TypeHandle
                            RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers nullableDamType when nullableDamType.UnderlyingTypeValue is ValueWithDynamicallyAccessedMembers underlyingTypeValue
                                => new NullableValueWithDynamicallyAccessedMembers(nullableDamType.NullableType, underlyingTypeValue),
                            RuntimeTypeHandleValue typeHandle
                                => new SystemTypeValue(typeHandle.RepresentedType),
                            RuntimeTypeHandleForGenericParameterValue genericParam
                                => _annotations.GetGenericParameterValue(genericParam.GenericParameter),
                            RuntimeTypeHandleForValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers
                                => valueWithDynamicallyAccessedMembers.UnderlyingTypeValue,
                            _ => annotatedMethodReturnValue
                        });
                    }
                    break;

                case IntrinsicId.Type_get_TypeHandle:
                    if (instanceValue.IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        if (value != NullValue.Instance)
                            AddReturnValue(value switch
                            {
                                NullableSystemTypeValue nullableSystemType
                                    => new RuntimeTypeHandleForNullableSystemTypeValue(nullableSystemType.NullableType, nullableSystemType.UnderlyingTypeValue),
                                NullableValueWithDynamicallyAccessedMembers nullableDamType when nullableDamType.UnderlyingTypeValue is GenericParameterValue genericParam
                                    => new RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers(nullableDamType.NullableType, new RuntimeTypeHandleForGenericParameterValue(genericParam.GenericParameter)),
                                NullableValueWithDynamicallyAccessedMembers nullableDamType
                                    => new RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers(nullableDamType.NullableType, nullableDamType.UnderlyingTypeValue),
                                SystemTypeValue typeHandle
                                    => new RuntimeTypeHandleValue(typeHandle.RepresentedType),
                                GenericParameterValue genericParam
                                    => new RuntimeTypeHandleForGenericParameterValue(genericParam.GenericParameter),
                                ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers
                                    => new RuntimeTypeHandleForValueWithDynamicallyAccessedMembers(valueWithDynamicallyAccessedMembers),
                                _ => annotatedMethodReturnValue
                            });
                        else
                            AddReturnValue(MultiValueLattice.Top);
                    }
                    break;

                // System.Reflection.MethodBase.GetMethodFromHandle(RuntimeMethodHandle handle)
                // System.Reflection.MethodBase.GetMethodFromHandle(RuntimeMethodHandle handle, RuntimeTypeHandle declaringType)
                case IntrinsicId.MethodBase_GetMethodFromHandle:
                {
                    if (argumentValues[0].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    // Infrastructure piece to support "ldtoken method -> GetMethodFromHandle"
                    foreach (var value in argumentValues[0].AsEnumerable())
                    {
                        if (value is RuntimeMethodHandleValue methodHandle)
                            AddReturnValue(new SystemReflectionMethodBaseValue(methodHandle.RepresentedMethod));
                        else
                            AddReturnValue(annotatedMethodReturnValue);
                    }
                }
                break;

                case IntrinsicId.MethodBase_get_MethodHandle:
                {
                    if (instanceValue.IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        if (value is SystemReflectionMethodBaseValue methodBaseValue)
                            AddReturnValue(new RuntimeMethodHandleValue(methodBaseValue.RepresentedMethod));
                        else
                            AddReturnValue(annotatedMethodReturnValue);
                    }
                }
                break;

                case IntrinsicId.TypeDelegator_Ctor:
                // This needs additional validation that the .ctor is called from a "newobj" instruction/operation
                // so it can't be done easily in shared code yet.
                case IntrinsicId.Array_Empty:
                // Array.Empty<T> must for now be handled by the specific implementation since it requires instantiated generic method handling
                case IntrinsicId.Object_GetType:
                    // Object.GetType requires additional handling by the caller to implement type hierarchy marking and related diagnostics
                    throw new NotImplementedException("These intrinsics should be handled by the specific implementation: " + intrinsicId);

                //
                // GetInterface(String)
                // GetInterface(String, bool)
                //
                case IntrinsicId.Type_GetInterface:
                {
                    if (instanceValue.IsEmpty() || argumentValues[0].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    var targetValue = _annotations.GetMethodThisParameterValue(calledMethod, DynamicallyAccessedMemberTypes.Interfaces);
                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        foreach (var interfaceName in argumentValues[0].AsEnumerable())
                        {
                            if (interfaceName == NullValue.Instance)
                            {
                                // Throws on null string, so no return value.
                                AddReturnValue(MultiValueLattice.Top);
                            }
                            else if (interfaceName is KnownStringValue stringValue && stringValue.Contents.Length == 0)
                            {
                                AddReturnValue(NullValue.Instance);
                            }
                            else
                            {
                                // For now no support for marking a single interface by name. We would have to correctly support
                                // mangled names for generics to do that correctly. Simply mark all interfaces on the type for now.

                                // Require Interfaces annotation
                                _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);

                                // Interfaces is transitive, so the return values will always have at least Interfaces annotation
                                DynamicallyAccessedMemberTypes returnMemberTypes = DynamicallyAccessedMemberTypes.Interfaces;

                                // Propagate All annotation across the call - All is a superset of Interfaces
                                if (value is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers
                                    && valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.All)
                                    returnMemberTypes = DynamicallyAccessedMemberTypes.All;

                                AddReturnValue(_annotations.GetMethodReturnValue(calledMethod, _isNewObj, returnMemberTypes));
                            }
                        }
                    }
                }
                break;

                //
                // GetInterfaces
                //
                case IntrinsicId.Type_GetInterfaces:
                {
                    if (instanceValue.IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    var targetValue = _annotations.GetMethodThisParameterValue(calledMethod, DynamicallyAccessedMemberTypes.Interfaces);
                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        // Require Interfaces annotation
                        _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);

                        // Interfaces is transitive, so the return values will always have at least Interfaces annotation
                        DynamicallyAccessedMemberTypes returnMemberTypes = DynamicallyAccessedMemberTypes.Interfaces;

                        // Propagate All annotation across the call - All is a superset of Interfaces
                        if (value is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers
                            && valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.All)
                            returnMemberTypes = DynamicallyAccessedMemberTypes.All;

                        // We model this as if each individual element of the returned array was returned from the GetInterfaces method call.
                        // It makes diagnostics fall out nicer because we now know where the Type comes from and where to assign blame, should the requirements not match
                        AddReturnValue(new ArrayOfAnnotatedSystemTypeValue(_annotations.GetMethodReturnValue(calledMethod, isNewObj: false, returnMemberTypes)));
                    }
                }
                break;


                //
                // AssemblyQualifiedName
                //
                case IntrinsicId.Type_get_AssemblyQualifiedName:
                {
                    if (instanceValue.IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        if (value is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers)
                        {
                            // Currently we don't need to track the difference between Type and String annotated values
                            // that only matters when we use them, so Type.GetType is the difference really.
                            // For diagnostics we actually don't want to track the Type.AssemblyQualifiedName
                            // as the annotation does not come from that call, but from its input.
                            AddReturnValue(valueWithDynamicallyAccessedMembers);
                        }
                        else if (value == NullValue.Instance)
                        {
                            // NullReferenceException, no return value.
                            AddReturnValue(MultiValueLattice.Top);
                        }
                        else
                        {
                            AddReturnValue(UnknownValue.Instance);
                        }
                    }
                }
                break;

                //
                // System.Runtime.CompilerServices.RuntimeHelpers
                //
                // RunClassConstructor(RuntimeTypeHandle type)
                //
                case IntrinsicId.RuntimeHelpers_RunClassConstructor:
                    if (argumentValues[0].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    foreach (var typeHandleValue in argumentValues[0].AsEnumerable())
                    {
                        if (typeHandleValue is RuntimeTypeHandleValue runtimeTypeHandleValue)
                        {
                            MarkStaticConstructor(runtimeTypeHandleValue.RepresentedType);
                        }
                        else if (typeHandleValue is RuntimeTypeHandleForValueWithDynamicallyAccessedMembers damAnnotatedHandle
                            && (damAnnotatedHandle.UnderlyingTypeValue.DynamicallyAccessedMemberTypes & DynamicallyAccessedMemberTypes.NonPublicConstructors) != 0)
                        {
                            // No action needed, NonPublicConstructors keeps the static constructor on the type
                        }
                        else if (typeHandleValue is RuntimeTypeHandleForNullableSystemTypeValue)
                        {
                            // No action needed, RunClassConstructor does not run the class constructor of the underlying type of Nullable<T>
                        }
                        else
                        {
                            _diagnosticContext.AddDiagnostic(DiagnosticId.UnrecognizedTypeInRuntimeHelpersRunClassConstructor, calledMethod.GetDisplayName());
                        }
                    }
                    break;

                //
                // GetConstructors(BindingFlags)
                // GetMethods(BindingFlags)
                // GetFields(BindingFlags)
                // GetEvents(BindingFlags)
                // GetProperties(BindingFlags)
                // GetNestedTypes(BindingFlags)
                // GetMembers(BindingFlags)
                //
                case IntrinsicId.Type_GetConstructors__BindingFlags:
                case IntrinsicId.Type_GetMethods__BindingFlags:
                case IntrinsicId.Type_GetFields__BindingFlags:
                case IntrinsicId.Type_GetProperties__BindingFlags:
                case IntrinsicId.Type_GetEvents__BindingFlags:
                case IntrinsicId.Type_GetNestedTypes__BindingFlags:
                case IntrinsicId.Type_GetMembers__BindingFlags:
                {
                    BindingFlags? bindingFlags;
                    bindingFlags = GetBindingFlagsFromValue(argumentValues[0]);
                    DynamicallyAccessedMemberTypes memberTypes;
                    if (BindingFlagsAreUnsupported(bindingFlags))
                    {
                        memberTypes = intrinsicId switch
                        {
                            IntrinsicId.Type_GetConstructors__BindingFlags => DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors,
                            IntrinsicId.Type_GetMethods__BindingFlags => DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods,
                            IntrinsicId.Type_GetEvents__BindingFlags => DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents,
                            IntrinsicId.Type_GetFields__BindingFlags => DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields,
                            IntrinsicId.Type_GetProperties__BindingFlags => DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties,
                            IntrinsicId.Type_GetNestedTypes__BindingFlags => DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes,
                            IntrinsicId.Type_GetMembers__BindingFlags => DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors |
                                DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents |
                                DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields |
                                DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods |
                                DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties |
                                DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes,
                            _ => throw new ArgumentException($"Reflection call '{calledMethod.GetDisplayName()}' inside '{GetContainingSymbolDisplayName()}' is of unexpected member type."),
                        };
                    }
                    else
                    {
                        memberTypes = intrinsicId switch
                        {
                            IntrinsicId.Type_GetConstructors__BindingFlags => GetDynamicallyAccessedMemberTypesFromBindingFlagsForConstructors(bindingFlags),
                            IntrinsicId.Type_GetMethods__BindingFlags => GetDynamicallyAccessedMemberTypesFromBindingFlagsForMethods(bindingFlags),
                            IntrinsicId.Type_GetEvents__BindingFlags => GetDynamicallyAccessedMemberTypesFromBindingFlagsForEvents(bindingFlags),
                            IntrinsicId.Type_GetFields__BindingFlags => GetDynamicallyAccessedMemberTypesFromBindingFlagsForFields(bindingFlags),
                            IntrinsicId.Type_GetProperties__BindingFlags => GetDynamicallyAccessedMemberTypesFromBindingFlagsForProperties(bindingFlags),
                            IntrinsicId.Type_GetNestedTypes__BindingFlags => GetDynamicallyAccessedMemberTypesFromBindingFlagsForNestedTypes(bindingFlags),
                            IntrinsicId.Type_GetMembers__BindingFlags => GetDynamicallyAccessedMemberTypesFromBindingFlagsForMembers(bindingFlags),
                            _ => throw new ArgumentException($"Reflection call '{calledMethod.GetDisplayName()}' inside '{GetContainingSymbolDisplayName()}' is of unexpected member type."),
                        };
                    }

                    var targetValue = _annotations.GetMethodThisParameterValue(calledMethod, memberTypes);
                    _requireDynamicallyAccessedMembersAction.Invoke(instanceValue, targetValue);
                }
                break;

                //
                // GetField(string)
                // GetField(string, BindingFlags)
                // GetEvent(string)
                // GetEvent(string, BindingFlags)
                // GetProperty(string)
                // GetProperty(string, BindingFlags)
                // GetProperty(string, Type)
                // GetProperty(string, Type[])
                // GetProperty(string, Type, Type[])
                // GetProperty(string, Type, Type[], ParameterModifier[])
                // GetProperty(string, BindingFlags, Binder, Type, Type[], ParameterModifier[])
                //
                case IntrinsicId.Type_GetField:
                case IntrinsicId.Type_GetProperty:
                case IntrinsicId.Type_GetEvent:
                {
                    if (instanceValue.IsEmpty() || argumentValues[0].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    BindingFlags? bindingFlags;
                    if (calledMethod.HasParameterOfType((ParameterIndex)2, "System.Reflection.BindingFlags"))
                        bindingFlags = GetBindingFlagsFromValue(argumentValues[1]);
                    else
                        // Assume a default value for BindingFlags for methods that don't use BindingFlags as a parameter
                        bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;

                    DynamicallyAccessedMemberTypes memberTypes = intrinsicId switch
                    {
                        IntrinsicId.Type_GetEvent => GetDynamicallyAccessedMemberTypesFromBindingFlagsForEvents(bindingFlags),
                        IntrinsicId.Type_GetField => GetDynamicallyAccessedMemberTypesFromBindingFlagsForFields(bindingFlags),
                        IntrinsicId.Type_GetProperty => GetDynamicallyAccessedMemberTypesFromBindingFlagsForProperties(bindingFlags),
                        _ => throw new ArgumentException($"Reflection call '{calledMethod.GetDisplayName()}' inside '{GetContainingSymbolDisplayName()}' is of unexpected member type."),
                    };

                    var targetValue = _annotations.GetMethodThisParameterValue(calledMethod, memberTypes);
                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        if (value is SystemTypeValue systemTypeValue)
                        {
                            foreach (var stringParam in argumentValues[0].AsEnumerable())
                            {
                                if (stringParam is KnownStringValue stringValue && !BindingFlagsAreUnsupported(bindingFlags))
                                {
                                    switch (intrinsicId)
                                    {
                                        case IntrinsicId.Type_GetEvent:
                                            MarkEventsOnTypeHierarchy(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags);
                                            break;
                                        case IntrinsicId.Type_GetField:
                                            MarkFieldsOnTypeHierarchy(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags);
                                            break;
                                        case IntrinsicId.Type_GetProperty:
                                            MarkPropertiesOnTypeHierarchy(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags);
                                            break;
                                        default:
                                            Debug.Fail("Unreachable.");
                                            break;
                                    }
                                }
                                else
                                {
                                    _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                                }
                            }
                        }
                        else
                        {
                            _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                        }
                    }
                }
                break;

                //
                // GetMember(String)
                // GetMember(String, BindingFlags)
                // GetMember(String, MemberTypes, BindingFlags)
                //
                case IntrinsicId.Type_GetMember:
                {
                    if (instanceValue.IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    BindingFlags? bindingFlags;
                    if (calledMethod.HasMetadataParametersCount(1))
                    {
                        // Assume a default value for BindingFlags for methods that don't use BindingFlags as a parameter
                        bindingFlags = BindingFlags.Public | BindingFlags.Instance;
                    }
                    else if (calledMethod.HasMetadataParametersCount(2) && calledMethod.HasParameterOfType((ParameterIndex)2, "System.Reflection.BindingFlags"))
                        bindingFlags = GetBindingFlagsFromValue(argumentValues[1]);
                    else if (calledMethod.HasMetadataParametersCount(3) && calledMethod.HasParameterOfType((ParameterIndex)3, "System.Reflection.BindingFlags"))
                    {
                        bindingFlags = GetBindingFlagsFromValue(argumentValues[2]);
                    }
                    else // Non recognized intrinsic
                        throw new ArgumentException($"Reflection call '{calledMethod.GetDisplayName()}' inside '{GetContainingSymbolDisplayName()}' is an unexpected intrinsic.");

                    DynamicallyAccessedMemberTypes requiredMemberTypes;
                    if (BindingFlagsAreUnsupported(bindingFlags))
                    {
                        requiredMemberTypes = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors |
                            DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents |
                            DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields |
                            DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods |
                            DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties |
                            DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;
                    }
                    else
                    {
                        requiredMemberTypes = GetDynamicallyAccessedMemberTypesFromBindingFlagsForMembers(bindingFlags);
                    }

                    var targetValue = _annotations.GetMethodThisParameterValue(calledMethod, requiredMemberTypes);

                    // Go over all types we've seen
                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        // Mark based on bitfield requirements
                        _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                    }
                }
                break;

                //
                // GetMethod(string)
                // GetMethod(string, BindingFlags)
                // GetMethod(string, Type[])
                // GetMethod(string, Type[], ParameterModifier[])
                // GetMethod(string, BindingFlags, Type[])
                // GetMethod(string, BindingFlags, Binder, Type[], ParameterModifier[])
                // GetMethod(string, BindingFlags, Binder, CallingConventions, Type[], ParameterModifier[])
                // GetMethod(string, int, Type[])
                // GetMethod(string, int, Type[], ParameterModifier[]?)
                // GetMethod(string, int, BindingFlags, Type[])
                // GetMethod(string, int, BindingFlags, Binder?, Type[], ParameterModifier[]?)
                // GetMethod(string, int, BindingFlags, Binder?, CallingConventions, Type[], ParameterModifier[]?)
                //
                case IntrinsicId.Type_GetMethod:
                {
                    if (instanceValue.IsEmpty() || argumentValues[0].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    BindingFlags? bindingFlags;
                    if (calledMethod.HasParameterOfType((ParameterIndex)2, "System.Reflection.BindingFlags"))
                        bindingFlags = GetBindingFlagsFromValue(argumentValues[1]);
                    else if (calledMethod.HasParameterOfType((ParameterIndex)3, "System.Reflection.BindingFlags"))
                        bindingFlags = GetBindingFlagsFromValue(argumentValues[2]);
                    else
                        // Assume a default value for BindingFlags for methods that don't use BindingFlags as a parameter
                        bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;

                    var targetValue = _annotations.GetMethodThisParameterValue(calledMethod, GetDynamicallyAccessedMemberTypesFromBindingFlagsForMethods(bindingFlags));
                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        if (value is SystemTypeValue systemTypeValue)
                        {
                            foreach (var stringParam in argumentValues[0].AsEnumerable())
                            {
                                if (stringParam is KnownStringValue stringValue && !BindingFlagsAreUnsupported(bindingFlags))
                                {
                                    AddReturnValue(MultiValueLattice.Top); ; // Initialize return value (so that it's not autofilled if there are no matching methods)
                                    foreach (var methodValue in ProcessGetMethodByName(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags))
                                        AddReturnValue(methodValue);
                                }
                                else if (stringParam is NullValue)
                                {
                                    // GetMethod(null) throws - so track empty value set as its result
                                    AddReturnValue(MultiValueLattice.Top);
                                }
                                else
                                {
                                    // Otherwise fall back to the bitfield requirements
                                    _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                                    AddReturnValue(annotatedMethodReturnValue);
                                }
                            }
                        }
                        else if (value is NullValue)
                        {
                            // null.GetMethod(...) throws - so track empty value set as its result
                            AddReturnValue(MultiValueLattice.Top);
                        }
                        else if (value is MethodReturnValue returnValueType &&
                            returnValueType.Method.IsDeclaredOnType("System.Reflection.EventInfo") &&
                            returnValueType.Method.Name == "get_EventHandlerType")
                        {
                            foreach (var stringParam in argumentValues[0].AsEnumerable())
                            {
                                if (stringParam is KnownStringValue stringValue && stringValue.Contents == "Invoke")
                                {
                                    // EventInfo.EventHandlerType.GetMethod("Invoke") is trim-safe because we keep the Invoke method on delegates
                                    AddReturnValue(MultiValueLattice.Top);
                                }
                                else
                                {
                                    // Otherwise fall back to the bitfield requirements
                                    _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                                    AddReturnValue(annotatedMethodReturnValue);
                                }
                            }
                        }
                        else
                        {
                            // Otherwise fall back to the bitfield requirements
                            _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                            AddReturnValue(annotatedMethodReturnValue);
                        }
                    }
                }
                break;

                //
                // GetNestedType(string)
                // GetNestedType(string, BindingFlags)
                //
                case IntrinsicId.Type_GetNestedType:
                {
                    if (instanceValue.IsEmpty() || argumentValues[0].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    const DynamicallyAccessedMemberTypes ImplicitNestedTypeAccessLevel =
                        DynamicallyAccessedMemberTypesEx.PublicConstructorsWithInherited | DynamicallyAccessedMemberTypesEx.NonPublicConstructorsWithInherited |
                        DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypesEx.NonPublicMethodsWithInherited |
                        DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypesEx.NonPublicFieldsWithInherited |
                        DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypesEx.NonPublicPropertiesWithInherited |
                        DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypesEx.NonPublicEventsWithInherited |
                        DynamicallyAccessedMemberTypesEx.PublicNestedTypesWithInherited | DynamicallyAccessedMemberTypesEx.NonPublicNestedTypesWithInherited |
                        DynamicallyAccessedMemberTypes.Interfaces;

                    BindingFlags? bindingFlags;
                    if (calledMethod.HasParameterOfType((ParameterIndex)2, "System.Reflection.BindingFlags"))
                        bindingFlags = GetBindingFlagsFromValue(argumentValues[1]);
                    else
                        // Assume a default value for BindingFlags for methods that don't use BindingFlags as a parameter
                        bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;

                    var targetValue = _annotations.GetMethodThisParameterValue(calledMethod, GetDynamicallyAccessedMemberTypesFromBindingFlagsForNestedTypes(bindingFlags));
                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        if (value is SystemTypeValue systemTypeValue)
                        {
                            foreach (var stringParam in argumentValues[0].AsEnumerable())
                            {
                                if (stringParam is KnownStringValue stringValue && !BindingFlagsAreUnsupported(bindingFlags))
                                {
                                    AddReturnValue(MultiValueLattice.Top);
                                    foreach (var nestedTypeValue in GetNestedTypesOnType(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags))
                                    {
                                        MarkType(nestedTypeValue.RepresentedType);
                                        AddReturnValue(nestedTypeValue);
                                    }
                                }
                                else if (stringParam is NullValue)
                                {
                                    AddReturnValue(MultiValueLattice.Top);
                                }
                                else
                                {
                                    // Otherwise fall back to the bitfield requirements
                                    _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);

                                    // We only applied the annotation based on binding flags, so we will keep the necessary types
                                    // and we keep the set of implicitly available members on them.
                                    AddReturnValue(_annotations.GetMethodReturnValue(calledMethod, _isNewObj, ImplicitNestedTypeAccessLevel));
                                }
                            }
                        }
                        else if (value is NullValue)
                        {
                            // null.GetNestedType(..) throws - so track empty value set
                            AddReturnValue(MultiValueLattice.Top);
                        }
                        else
                        {
                            // Otherwise fall back to the bitfield requirements
                            _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);

                            // If the input is an annotated value which has All - we can propagate that to the return value
                            // since All applies recursively to all nested type (see MarkStep.MarkEntireType).
                            // Otherwise we mark the nested type with implicitly available members on it.
                            if (value is ValueWithDynamicallyAccessedMembers { DynamicallyAccessedMemberTypes: DynamicallyAccessedMemberTypes.All })
                                AddReturnValue(_annotations.GetMethodReturnValue(calledMethod, _isNewObj, DynamicallyAccessedMemberTypes.All));
                            else
                                AddReturnValue(_annotations.GetMethodReturnValue(calledMethod, _isNewObj, ImplicitNestedTypeAccessLevel));
                        }
                    }
                }
                break;

                //
                // System.Reflection.RuntimeReflectionExtensions
                //
                // static GetRuntimeEvent(this Type type, string name)
                // static GetRuntimeField(this Type type, string name)
                // static GetRuntimeMethod(this Type type, string name, Type[] parameters)
                // static GetRuntimeProperty(this Type type, string name)
                //
                case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeEvent:
                case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeField:
                case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeMethod:
                case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeProperty:
                {

                    if (argumentValues[0].IsEmpty() || argumentValues[1].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
                    DynamicallyAccessedMemberTypes requiredMemberTypes = intrinsicId switch
                    {
                        IntrinsicId.RuntimeReflectionExtensions_GetRuntimeEvent => DynamicallyAccessedMemberTypes.PublicEvents,
                        IntrinsicId.RuntimeReflectionExtensions_GetRuntimeField => DynamicallyAccessedMemberTypes.PublicFields,
                        IntrinsicId.RuntimeReflectionExtensions_GetRuntimeMethod => DynamicallyAccessedMemberTypes.PublicMethods,
                        IntrinsicId.RuntimeReflectionExtensions_GetRuntimeProperty => DynamicallyAccessedMemberTypes.PublicProperties,
                        _ => throw new ArgumentException($"Reflection call '{calledMethod.GetDisplayName()}' inside '{GetContainingSymbolDisplayName()}' is of unexpected member type."),
                    };

                    var targetValue = _annotations.GetMethodParameterValue(new(calledMethod, (ParameterIndex)1), requiredMemberTypes);

                    foreach (var value in argumentValues[0].AsEnumerable())
                    {
                        if (value is SystemTypeValue systemTypeValue)
                        {
                            foreach (var stringParam in argumentValues[1].AsEnumerable())
                            {
                                if (stringParam is KnownStringValue stringValue)
                                {
                                    switch (intrinsicId)
                                    {
                                        case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeEvent:
                                            MarkEventsOnTypeHierarchy(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags);
                                            break;
                                        case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeField:
                                            MarkFieldsOnTypeHierarchy(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags);
                                            break;
                                        case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeMethod:
                                            AddReturnValue(MultiValueLattice.Top); // Initialize return value (so that it's not autofilled if there are no matching methods)
                                            foreach (var methodValue in ProcessGetMethodByName(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags))
                                                AddReturnValue(methodValue);
                                            break;
                                        case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeProperty:
                                            MarkPropertiesOnTypeHierarchy(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags);
                                            break;
                                        default:
                                            throw new ArgumentException($"Error processing reflection call '{calledMethod.GetDisplayName()}' inside {GetContainingSymbolDisplayName()}. Unexpected member kind.");
                                    }
                                }
                                else if (stringParam is NullValue)
                                {
                                    // GetRuntimeMethod(type, null) throws - so track empty value set as its result
                                    AddReturnValue(MultiValueLattice.Top);
                                }
                                else
                                {
                                    _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                                    AddReturnValue(annotatedMethodReturnValue);
                                }
                            }
                        }
                        else if (value is NullValue)
                        {
                            // GetRuntimeMethod(null, ...) throws - so track empty value set as its result
                            AddReturnValue(MultiValueLattice.Top);
                        }
                        else
                        {
                            _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                            AddReturnValue(annotatedMethodReturnValue);
                        }
                    }
                }
                break;

                //
                // System.Linq.Expressions.Expression
                //
                // static New(Type)
                //
                case IntrinsicId.Expression_New:
                {
                    var targetValue = _annotations.GetMethodParameterValue(new(calledMethod, (ParameterIndex)0), DynamicallyAccessedMemberTypes.PublicParameterlessConstructor);
                    foreach (var value in argumentValues[0].AsEnumerable())
                    {
                        if (value is SystemTypeValue systemTypeValue)
                        {
                            MarkConstructorsOnType(systemTypeValue.RepresentedType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, parameterCount: null);
                        }
                        else
                        {
                            _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                        }
                    }
                }
                break;

                //
                // System.Linq.Expressions.Expression
                //
                // static Property(Expression, MethodInfo)
                //
                case IntrinsicId.Expression_Property when calledMethod.HasParameterOfType((ParameterIndex)1, "System.Reflection.MethodInfo"):
                {
                    if (argumentValues[1].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    foreach (var value in argumentValues[1].AsEnumerable())
                    {
                        if (value is SystemReflectionMethodBaseValue methodBaseValue)
                        {
                            // We have one of the accessors for the property. The Expression.Property will in this case search
                            // for the matching PropertyInfo and store that. So to be perfectly correct we need to mark the
                            // respective PropertyInfo as "accessed via reflection".
                            if (MarkAssociatedProperty(methodBaseValue.RepresentedMethod))
                                continue;
                        }
                        else if (value == NullValue.Instance)
                        {
                            continue;
                        }

                        // In all other cases we may not even know which type this is about, so there's nothing we can do
                        // report it as a warning.
                        _diagnosticContext.AddDiagnostic(DiagnosticId.PropertyAccessorParameterInLinqExpressionsCannotBeStaticallyDetermined,
                            _annotations.GetMethodParameterValue(new(calledMethod, (ParameterIndex)1), DynamicallyAccessedMemberTypes.None).GetDiagnosticArgumentsForAnnotationMismatch().ToArray());
                    }
                }
                break;

                //
                // System.Linq.Expressions.Expression
                //
                // static Field(Expression, Type, String)
                // static Property(Expression, Type, String)
                //
                case IntrinsicId.Expression_Field:
                case IntrinsicId.Expression_Property:
                {
                    DynamicallyAccessedMemberTypes memberTypes = intrinsicId == IntrinsicId.Expression_Property
                        ? DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties
                        : DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields;

                    if (argumentValues[1].IsEmpty() || argumentValues[2].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    var targetValue = _annotations.GetMethodParameterValue(new(calledMethod, (ParameterIndex)1), memberTypes);
                    foreach (var value in argumentValues[1].AsEnumerable())
                    {
                        if (value is SystemTypeValue systemTypeValue)
                        {
                            foreach (var stringParam in argumentValues[2].AsEnumerable())
                            {
                                if (stringParam is KnownStringValue stringValue)
                                {
                                    BindingFlags bindingFlags = argumentValues[0].AsSingleValue() is NullValue ? BindingFlags.Static : BindingFlags.Default;
                                    if (intrinsicId == IntrinsicId.Expression_Property)
                                    {
                                        MarkPropertiesOnTypeHierarchy(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags);
                                    }
                                    else
                                    {
                                        MarkFieldsOnTypeHierarchy(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags);
                                    }
                                }
                                else if (stringParam is NullValue)
                                {
                                    // Null name will always throw, so there's nothing to do
                                }
                                else
                                {
                                    _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                                }
                            }
                        }
                        else
                        {
                            _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                        }
                    }
                }
                break;

                //
                // System.Linq.Expressions.Expression
                //
                // static Call(Type, String, Type[], Expression[])
                //
                case IntrinsicId.Expression_Call:
                {
                    BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;

                    var targetValue = _annotations.GetMethodParameterValue(
                        new ParameterProxy(calledMethod, (ParameterIndex)0),
                        GetDynamicallyAccessedMemberTypesFromBindingFlagsForMethods(bindingFlags));

                    // This is true even if we "don't know" - so it's only false if we're sure that there are no type arguments
                    bool hasTypeArguments = (argumentValues[2].AsSingleValue() as ArrayValue)?.Size.AsConstInt() != 0;
                    foreach (var value in argumentValues[0].AsEnumerable())
                    {
                        if (value is SystemTypeValue systemTypeValue)
                        {
                            foreach (var stringParam in argumentValues[1].AsEnumerable())
                            {
                                if (stringParam is KnownStringValue stringValue)
                                {
                                    foreach (var method in GetMethodsOnTypeHierarchy(systemTypeValue.RepresentedType, stringValue.Contents, bindingFlags))
                                    {
                                        ValidateGenericMethodInstantiation(method.RepresentedMethod, argumentValues[2], calledMethod);
                                        MarkMethod(method.RepresentedMethod);
                                    }
                                }
                                else
                                {
                                    if (hasTypeArguments)
                                    {
                                        // We don't know what method the `MakeGenericMethod` was called on, so we have to assume
                                        // that the method may have requirements which we can't fullfil -> warn.
                                        _diagnosticContext.AddDiagnostic(DiagnosticId.MakeGenericMethod, calledMethod.GetDisplayName());
                                    }

                                    _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                                }
                            }
                        }
                        else
                        {
                            if (hasTypeArguments)
                            {
                                // We don't know what method the `MakeGenericMethod` was called on, so we have to assume
                                // that the method may have requirements which we can't fullfil -> warn.
                                _diagnosticContext.AddDiagnostic(DiagnosticId.MakeGenericMethod, calledMethod.GetDisplayName());
                            }

                            _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                        }
                    }
                }
                break;

                //
                // Nullable.GetUnderlyingType(Type)
                //
                case IntrinsicId.Nullable_GetUnderlyingType:
                    if (argumentValues[0].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    foreach (var singlevalue in argumentValues[0].AsEnumerable())
                    {
                        AddReturnValue(singlevalue switch
                        {
                            SystemTypeValue systemType =>
                                systemType.RepresentedType.IsTypeOf("System", "Nullable`1")
                                    // This will happen if there's typeof(Nullable<>).MakeGenericType(unknown) - we know the return value is Nullable<>
                                    // but we don't know of what. So we represent it as known type, but not as known nullable type.
                                    // Has to be special cased here, since we need to return "unknown" type.
                                    ? annotatedMethodReturnValue
                                    : MultiValueLattice.Top, // This returns null at runtime, so return empty value
                            NullableSystemTypeValue nullableSystemType => nullableSystemType.UnderlyingTypeValue,
                            NullableValueWithDynamicallyAccessedMembers nullableDamValue => nullableDamValue.UnderlyingTypeValue,
                            ValueWithDynamicallyAccessedMembers damValue => damValue,
                            _ => annotatedMethodReturnValue
                        });
                    }
                    break;

                //
                // System.Type
                //
                // GetType(string)
                // GetType(string, Boolean)
                // GetType(string, Boolean, Boolean)
                // GetType(string, Func<AssemblyName, Assembly>, Func<Assembly, String, Boolean, Type>)
                // GetType(string, Func<AssemblyName, Assembly>, Func<Assembly, String, Boolean, Type>, Boolean)
                // GetType(string, Func<AssemblyName, Assembly>, Func<Assembly, String, Boolean, Type>, Boolean, Boolean)
                //
                case IntrinsicId.Type_GetType:
                {
                    if (argumentValues[0].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    if ((calledMethod.HasMetadataParametersCount(3) && calledMethod.HasParameterOfType((ParameterIndex)2, "System.Boolean") && argumentValues[2].AsConstInt() != 0) ||
                        (calledMethod.HasMetadataParametersCount(5) && argumentValues[4].AsConstInt() != 0))
                    {
                        _diagnosticContext.AddDiagnostic(DiagnosticId.CaseInsensitiveTypeGetTypeCallIsNotSupported, calledMethod.GetDisplayName());
                        returnValue = MultiValueLattice.Top; // This effectively disables analysis of anything which uses the return value
                        break;
                    }

                    foreach (var typeNameValue in argumentValues[0].AsEnumerable())
                    {
                        if (typeNameValue is KnownStringValue knownStringValue)
                        {
                            if (!_requireDynamicallyAccessedMembersAction.TryResolveTypeNameAndMark(knownStringValue.Contents, false, out TypeProxy foundType))
                            {
                                // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back.
                                AddReturnValue(MultiValueLattice.Top);
                            }
                            else
                            {
                                AddReturnValue(new SystemTypeValue(foundType));
                            }
                        }
                        else if (typeNameValue == NullValue.Instance)
                        {
                            // Nothing to do - this throws at runtime
                            AddReturnValue(MultiValueLattice.Top);
                        }
                        else if (typeNameValue is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers && valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes != 0)
                        {
                            // Propagate the annotation from the type name to the return value. Annotation on a string value will be fulfilled whenever a value is assigned to the string with annotation.
                            // So while we don't know which type it is, we can guarantee that it will fulfill the annotation.
                            AddReturnValue(_annotations.GetMethodReturnValue(calledMethod, _isNewObj, valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes));
                        }
                        else
                        {
                            _diagnosticContext.AddDiagnostic(DiagnosticId.UnrecognizedTypeNameInTypeGetType, calledMethod.GetDisplayName());
                            AddReturnValue(MultiValueLattice.Top);
                        }
                    }

                }
                break;

                //
                // System.Type
                //
                // Type MakeGenericType(params Type[] typeArguments)
                //
                case IntrinsicId.Type_MakeGenericType:
                    if (instanceValue.IsEmpty() || argumentValues[0].IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        if (value is SystemTypeValue typeValue)
                        {
                            // Special case Nullable<T>
                            // Nullables without a type argument are considered SystemTypeValues
                            if (typeValue.RepresentedType.IsTypeOf("System", "Nullable`1"))
                            {
                                // Note that we're not performing any generic parameter validation
                                // Special case: Nullable<T> where T : struct
                                //  The struct constraint in C# implies new() constraint, but Nullable doesn't make a use of that part.
                                //  There are several places even in the framework where typeof(Nullable<>).MakeGenericType would warn
                                //  without any good reason to do so.

                                foreach (var argumentValue in argumentValues[0].AsEnumerable())
                                {
                                    if ((argumentValue as ArrayValue)?.TryGetValueByIndex(0, out var underlyingMultiValue) == true)
                                    {
                                        foreach (var underlyingValue in underlyingMultiValue.AsEnumerable())
                                        {
                                            switch (underlyingValue)
                                            {
                                                // Don't warn on these types - it will throw instead
                                                case NullableValueWithDynamicallyAccessedMembers:
                                                case NullableSystemTypeValue:
                                                case SystemTypeValue maybeArrayValue when maybeArrayValue.RepresentedType.IsTypeOf("System", "Array"):
                                                    AddReturnValue(MultiValueLattice.Top);
                                                    break;
                                                case SystemTypeValue systemTypeValue:
                                                    AddReturnValue(new NullableSystemTypeValue(typeValue.RepresentedType, new SystemTypeValue(systemTypeValue.RepresentedType)));
                                                    break;
                                                // Generic Parameters and method parameters with annotations
                                                case ValueWithDynamicallyAccessedMembers damValue:
                                                    AddReturnValue(new NullableValueWithDynamicallyAccessedMembers(typeValue.RepresentedType, damValue));
                                                    break;
                                                // Everything else assume it has no annotations
                                                default:
                                                    // This returns just Nullable<> SystemTypeValue - so some things will work, but GetUnderlyingType won't propagate anything
                                                    // It's special cased to do that.
                                                    AddReturnValue(value);
                                                    break;
                                            }
                                        }
                                    }
                                    else
                                    {
                                        // This returns just Nullable<> SystemTypeValue - so some things will work, but GetUnderlyingType won't propagate anything
                                        // It's special cased to do that.
                                        AddReturnValue(value);
                                    }
                                }
                                // We want to skip adding the `value` to the return Value because we have already added Nullable<value>
                                continue;
                            }
                            else
                            {
                                // Any other type - perform generic parameter validation
                                var genericParameterValues = GetGenericParameterValues(typeValue.RepresentedType.GetGenericParameters());
                                if (!AnalyzeGenericInstantiationTypeArray(argumentValues[0], genericParameterValues))
                                {
                                    _diagnosticContext.AddDiagnostic(DiagnosticId.MakeGenericType, calledMethod.GetDisplayName());
                                }
                            }
                        }
                        else if (value == NullValue.Instance)
                        {
                            // At runtime this would throw - so it has no effect on analysis
                            AddReturnValue(MultiValueLattice.Top);
                        }
                        else
                        {
                            // We have no way to "include more" to fix this if we don't know, so we have to warn
                            _diagnosticContext.AddDiagnostic(DiagnosticId.MakeGenericType, calledMethod.GetDisplayName());
                        }

                        // We don't want to lose track of the type
                        // in case this is e.g. Activator.CreateInstance(typeof(Foo<>).MakeGenericType(...));
                        // Note this is not called in the Nullable case - we skipt this via the 'continue'.
                        AddReturnValue(value);
                    }
                    break;

                //
                // Type.BaseType
                //
                case IntrinsicId.Type_get_BaseType:
                {
                    if (instanceValue.IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        if (value is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers)
                        {
                            DynamicallyAccessedMemberTypes propagatedMemberTypes = DynamicallyAccessedMemberTypes.None;
                            if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.All)
                                propagatedMemberTypes = DynamicallyAccessedMemberTypes.All;
                            else
                            {
                                // PublicConstructors are not propagated to base type

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypesEx.PublicConstructorsWithInherited))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypesEx.PublicConstructorsWithInherited;

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypesEx.NonPublicConstructorsWithInherited))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypesEx.NonPublicConstructorsWithInherited;

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypes.PublicEvents))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypes.PublicEvents;

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypesEx.NonPublicEventsWithInherited))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypesEx.NonPublicEventsWithInherited;

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypes.PublicFields))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypes.PublicFields;

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypesEx.NonPublicFieldsWithInherited))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypesEx.NonPublicFieldsWithInherited;

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypes.PublicMethods))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypes.PublicMethods;

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypesEx.NonPublicMethodsWithInherited))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypesEx.NonPublicMethodsWithInherited;

                                // PublicNestedTypes are not propagated to base type

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypesEx.PublicNestedTypesWithInherited))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypesEx.PublicNestedTypesWithInherited;

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypesEx.NonPublicNestedTypesWithInherited))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypesEx.NonPublicNestedTypesWithInherited;

                                // PublicParameterlessConstructor is not propagated to base type

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypes.PublicProperties))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypes.PublicProperties;

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypesEx.NonPublicPropertiesWithInherited))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypesEx.NonPublicPropertiesWithInherited;

                                if (valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes.HasFlag(DynamicallyAccessedMemberTypes.Interfaces))
                                    propagatedMemberTypes |= DynamicallyAccessedMemberTypes.Interfaces;
                            }

                            AddReturnValue(_annotations.GetMethodReturnValue(calledMethod, _isNewObj, propagatedMemberTypes));
                        }
                        else if (value is SystemTypeValue systemTypeValue)
                        {
                            if (TryGetBaseType(systemTypeValue.RepresentedType, out var baseType))
                                AddReturnValue(new SystemTypeValue(baseType.Value));
                            else
                                AddReturnValue(annotatedMethodReturnValue);
                        }
                        else if (value == NullValue.Instance)
                        {
                            // Ignore nulls - null.BaseType will fail at runtime, but it has no effect on static analysis
                            AddReturnValue(MultiValueLattice.Top);
                            continue;
                        }
                        else
                        {
                            // Unknown input - propagate a return value without any annotation - we know it's a Type but we know nothing about it
                            AddReturnValue(annotatedMethodReturnValue);
                        }
                    }
                }
                break;

                //
                // GetConstructor(Type[])
                // GetConstructor(BindingFlags, Type[])
                // GetConstructor(BindingFlags, Binder, Type[], ParameterModifier [])
                // GetConstructor(BindingFlags, Binder, CallingConventions, Type[], ParameterModifier [])
                //
                case IntrinsicId.Type_GetConstructor:
                {
                    if (instanceValue.IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    BindingFlags? bindingFlags;
                    if (calledMethod.HasParameterOfType((ParameterIndex)1, "System.Reflection.BindingFlags"))
                        bindingFlags = GetBindingFlagsFromValue(argumentValues[0]);
                    else
                        // Assume a default value for BindingFlags for methods that don't use BindingFlags as a parameter
                        bindingFlags = BindingFlags.Public | BindingFlags.Instance;

                    int? ctorParameterCount = calledMethod.GetMetadataParametersCount() switch
                    {
                        1 => (argumentValues[0].AsSingleValue() as ArrayValue)?.Size.AsConstInt(),
                        2 => (argumentValues[1].AsSingleValue() as ArrayValue)?.Size.AsConstInt(),
                        4 => (argumentValues[2].AsSingleValue() as ArrayValue)?.Size.AsConstInt(),
                        5 => (argumentValues[3].AsSingleValue() as ArrayValue)?.Size.AsConstInt(),
                        _ => null,
                    };

                    // Go over all types we've seen
                    foreach (var value in instanceValue.AsEnumerable())
                    {
                        if (value is SystemTypeValue systemTypeValue && !BindingFlagsAreUnsupported(bindingFlags))
                        {
                            if (HasBindingFlag(bindingFlags, BindingFlags.Public) && !HasBindingFlag(bindingFlags, BindingFlags.NonPublic)
                                && ctorParameterCount == 0)
                            {
                                MarkPublicParameterlessConstructorOnType(systemTypeValue.RepresentedType);
                            }
                            else
                            {
                                MarkConstructorsOnType(systemTypeValue.RepresentedType, bindingFlags, parameterCount: null);
                            }
                        }
                        else
                        {
                            // Otherwise fall back to the bitfield requirements
                            var requiredMemberTypes = GetDynamicallyAccessedMemberTypesFromBindingFlagsForConstructors(bindingFlags);
                            // We can scope down the public constructors requirement if we know the number of parameters is 0
                            if (requiredMemberTypes == DynamicallyAccessedMemberTypes.PublicConstructors && ctorParameterCount == 0)
                                requiredMemberTypes = DynamicallyAccessedMemberTypes.PublicParameterlessConstructor;

                            var targetValue = _annotations.GetMethodThisParameterValue(calledMethod, requiredMemberTypes);
                            _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                        }
                    }
                }
                break;

                //
                // System.Reflection.MethodInfo
                //
                // MakeGenericMethod(Type[] typeArguments)
                //
                case IntrinsicId.MethodInfo_MakeGenericMethod:
                {
                    if (instanceValue.IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

                    foreach (var methodValue in instanceValue.AsEnumerable())
                    {
                        if (methodValue is SystemReflectionMethodBaseValue methodBaseValue)
                        {
                            ValidateGenericMethodInstantiation(methodBaseValue.RepresentedMethod, argumentValues[0], calledMethod);
                        }
                        else if (methodValue == NullValue.Instance)
                        {
                            // Nothing to do
                        }
                        else
                        {
                            // We don't know what method the `MakeGenericMethod` was called on, so we have to assume
                            // that the method may have requirements which we can't fullfil -> warn.
                            _diagnosticContext.AddDiagnostic(DiagnosticId.MakeGenericMethod, calledMethod.GetDisplayName());
                        }
                    }

                    // MakeGenericMethod doesn't change the identity of the MethodBase we're tracking so propagate to the return value
                    AddReturnValue(instanceValue);
                }
                break;

                //
                // System.Activator
                //
                // static CreateInstance(System.Type type)
                // static CreateInstance(System.Type type, bool nonPublic)
                // static CreateInstance(System.Type type, params object?[]? args)
                // static CreateInstance(System.Type type, object?[]? args, object?[]? activationAttributes)
                // static CreateInstance(System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture)
                // static CreateInstance(System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture, object?[]? activationAttributes) { throw null; }
                //
                case IntrinsicId.Activator_CreateInstance__Type:
                {
                    int? ctorParameterCount = null;
                    BindingFlags bindingFlags = BindingFlags.Instance;
                    if (calledMethod.GetMetadataParametersCount() > 1)
                    {
                        if (calledMethod.HasParameterOfType((ParameterIndex)1, "System.Boolean"))
                        {
                            // The overload that takes a "nonPublic" bool
                            bool nonPublic = argumentValues[1].AsConstInt() != 0;

                            if (nonPublic)
                                bindingFlags |= BindingFlags.NonPublic | BindingFlags.Public;
                            else
                                bindingFlags |= BindingFlags.Public;
                            ctorParameterCount = 0;
                        }
                        else
                        {
                            // Overload that has the parameters as the second or fourth argument
                            int argsParam = calledMethod.HasMetadataParametersCount(2) || calledMethod.HasMetadataParametersCount(3) ? 1 : 3;

                            if (argumentValues.Count > argsParam)
                            {
                                if (argumentValues[argsParam].AsSingleValue() is ArrayValue arrayValue &&
                                    arrayValue.Size.AsConstInt() != null)
                                    ctorParameterCount = arrayValue.Size.AsConstInt();
                                else if (argumentValues[argsParam].AsSingleValue() is NullValue)
                                    ctorParameterCount = 0;
                            }

                            if (calledMethod.GetMetadataParametersCount() > 3)
                            {
                                if (argumentValues[1].AsConstInt() is int constInt)
                                    bindingFlags |= (BindingFlags)constInt;
                                else
                                    bindingFlags |= BindingFlags.NonPublic | BindingFlags.Public;
                            }
                            else
                            {
                                bindingFlags |= BindingFlags.Public;
                            }
                        }
                    }
                    else
                    {
                        // The overload with a single System.Type argument
                        ctorParameterCount = 0;
                        bindingFlags |= BindingFlags.Public;
                    }

                    // Go over all types we've seen
                    foreach (var value in argumentValues[0].AsEnumerable())
                    {
                        if (value is SystemTypeValue systemTypeValue)
                        {
                            // Special case known type values as we can do better by applying exact binding flags and parameter count.
                            MarkConstructorsOnType(systemTypeValue.RepresentedType, bindingFlags, ctorParameterCount);
                        }
                        else
                        {
                            // Otherwise fall back to the bitfield requirements
                            var requiredMemberTypes = GetDynamicallyAccessedMemberTypesFromBindingFlagsForConstructors(bindingFlags);

                            // Special case the public parameterless constructor if we know that there are 0 args passed in
                            if (ctorParameterCount == 0 && requiredMemberTypes.HasFlag(DynamicallyAccessedMemberTypes.PublicConstructors))
                            {
                                requiredMemberTypes &= ~DynamicallyAccessedMemberTypes.PublicConstructors;
                                requiredMemberTypes |= DynamicallyAccessedMemberTypes.PublicParameterlessConstructor;
                            }

                            var targetValue = _annotations.GetMethodParameterValue(new(calledMethod, (ParameterIndex)0), requiredMemberTypes);

                            _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                        }
                    }
                }
                break;

                //
                // System.Activator
                //
                // static CreateInstance(string assemblyName, string typeName)
                // static CreateInstance(string assemblyName, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture, object?[]? activationAttributes)
                // static CreateInstance(string assemblyName, string typeName, object?[]? activationAttributes)
                //
                case IntrinsicId.Activator_CreateInstance__AssemblyName_TypeName:
                    ProcessCreateInstanceByName(calledMethod, argumentValues);
                    break;

                //
                // System.Activator
                //
                // static CreateInstanceFrom(string assemblyFile, string typeName)
                // static CreateInstanceFrom(string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes)
                // static CreateInstanceFrom(string assemblyFile, string typeName, object? []? activationAttributes)
                //
                case IntrinsicId.Activator_CreateInstanceFrom:
                    ProcessCreateInstanceByName(calledMethod, argumentValues);
                    break;

                //
                // System.AppDomain
                //
                // CreateInstance(string assemblyName, string typeName)
                // CreateInstance(string assemblyName, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes)
                // CreateInstance(string assemblyName, string typeName, object? []? activationAttributes)
                //
                // CreateInstanceAndUnwrap(string assemblyName, string typeName)
                // CreateInstanceAndUnwrap(string assemblyName, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes)
                // CreateInstanceAndUnwrap(string assemblyName, string typeName, object? []? activationAttributes)
                //
                // CreateInstanceFrom(string assemblyFile, string typeName)
                // CreateInstanceFrom(string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes)
                // CreateInstanceFrom(string assemblyFile, string typeName, object? []? activationAttributes)
                //
                // CreateInstanceFromAndUnwrap(string assemblyFile, string typeName)
                // CreateInstanceFromAndUnwrap(string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes)
                // CreateInstanceFromAndUnwrap(string assemblyFile, string typeName, object? []? activationAttributes)
                //
                case IntrinsicId.AppDomain_CreateInstance:
                case IntrinsicId.AppDomain_CreateInstanceAndUnwrap:
                case IntrinsicId.AppDomain_CreateInstanceFrom:
                case IntrinsicId.AppDomain_CreateInstanceFromAndUnwrap:
                    ProcessCreateInstanceByName(calledMethod, argumentValues);
                    break;

                //
                // System.Reflection.Assembly
                //
                // CreateInstance(string typeName)
                // CreateInstance(string typeName, bool ignoreCase)
                // CreateInstance(string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder? binder, object []? args, CultureInfo? culture, object []? activationAttributes)
                //
                case IntrinsicId.Assembly_CreateInstance:
                    // For now always fail since we don't track assemblies (dotnet/linker/issues/1947)
                    _diagnosticContext.AddDiagnostic(DiagnosticId.ParametersOfAssemblyCreateInstanceCannotBeAnalyzed, calledMethod.GetDisplayName());
                    break;

                case IntrinsicId.None:
                    // Verify the argument values match the annotations on the parameter definition
                    if (requiresDataFlowAnalysis)
                    {
                        foreach (var parameter in calledMethod.GetParameters())
                        {
                            if (parameter.GetReferenceKind() is ReferenceKind.Out)
                                continue;
                            if (parameter.IsImplicitThis)
                            {
                                _requireDynamicallyAccessedMembersAction.Invoke(instanceValue, _annotations.GetMethodThisParameterValue(calledMethod));
                                continue;
                            }
                            _requireDynamicallyAccessedMembersAction.Invoke(argumentValues[parameter.MetadataIndex], _annotations.GetMethodParameterValue(parameter));
                        }
                    }
                    break;

                default:
                    return false;
            }

            if (MethodIsTypeConstructor(calledMethod))
                returnValue = UnknownValue.Instance;

            methodReturnValue = returnValue;
            return true;

            void AddReturnValue(MultiValue value)
            {
                returnValue = (returnValue == null) ? value : MultiValueLattice.Meet(returnValue.Value, value);
            }
        }

        private IEnumerable<MultiValue> ProcessGetMethodByName(TypeProxy type, string methodName, BindingFlags? bindingFlags)
        {
            bool foundAny = false;
            foreach (var method in GetMethodsOnTypeHierarchy(type, methodName, bindingFlags))
            {
                MarkMethod(method.RepresentedMethod);
                yield return method;
                foundAny = true;
            }

            // If there were no methods found the API will return null at runtime, so we should
            // track the null as a return value as well.
            // This also prevents warnings in such case, since if we don't set the return value it will be
            // "unknown" and consumers may warn.
            if (!foundAny)
                yield return NullValue.Instance;
        }

        private bool AnalyzeGenericInstantiationTypeArray(in MultiValue arrayParam, ImmutableArray<GenericParameterValue> genericParameters)
        {
            bool hasRequirements = false;
            foreach (var genericParameter in genericParameters)
            {
                if (GetGenericParameterEffectiveMemberTypes(genericParameter) != DynamicallyAccessedMemberTypes.None)
                {
                    hasRequirements = true;
                    break;
                }
            }

            // If there are no requirements, then there's no point in warning
            if (!hasRequirements)
                return true;

            foreach (var typesValue in arrayParam.AsEnumerable())
            {
                if (typesValue is not ArrayValue array)
                {
                    return false;
                }

                int? size = array.Size.AsConstInt();
                if (size == null || size != genericParameters.Length)
                {
                    return false;
                }

                bool allIndicesKnown = true;
                for (int i = 0; i < size.Value; i++)
                {
                    if (!array.TryGetValueByIndex(i, out MultiValue value) || value.AsSingleValue() is UnknownValue)
                    {
                        allIndicesKnown = false;
                        break;
                    }
                }

                if (!allIndicesKnown)
                {
                    return false;
                }

                for (int i = 0; i < size.Value; i++)
                {
                    if (array.TryGetValueByIndex(i, out MultiValue value))
                    {
                        var targetValue = _annotations.GetGenericParameterValue(genericParameters[i].GenericParameter, GetGenericParameterEffectiveMemberTypes(genericParameters[i]));
                        _requireDynamicallyAccessedMembersAction.Invoke(value, targetValue);
                    }
                }
            }
            return true;

            // Returns effective annotation of a generic parameter where it incorporates the constraint into the annotation.
            // There are basically three cases where the constraint matters:
            // - NeedsNew<SpecificType> - MarkStep will simply mark the default .ctor of SpecificType in this case, it has nothing to do with reflection
            // - NeedsNew<TOuter> - this should be validated by the compiler/IL - TOuter must have matching constraints by definition, so nothing to validate
            // - typeof(NeedsNew<>).MakeGenericType(typeOuter) - for this case we have to do it by hand as it's reflection. This is where this method helps.
            static DynamicallyAccessedMemberTypes GetGenericParameterEffectiveMemberTypes(GenericParameterValue genericParameter)
            {
                DynamicallyAccessedMemberTypes result = genericParameter.DynamicallyAccessedMemberTypes;
                if (genericParameter.GenericParameter.HasDefaultConstructorConstraint())
                    result |= DynamicallyAccessedMemberTypes.PublicParameterlessConstructor;

                return result;
            }
        }

        private void ValidateGenericMethodInstantiation(
            MethodProxy genericMethod,
            in MultiValue genericParametersArray,
            MethodProxy reflectionMethod)
        {
            if (!genericMethod.HasGenericParameters())
            {
                return;
            }

            var genericParameterValues = GetGenericParameterValues(genericMethod.GetGenericParameters());
            if (!AnalyzeGenericInstantiationTypeArray(genericParametersArray, genericParameterValues))
            {
                _diagnosticContext.AddDiagnostic(DiagnosticId.MakeGenericMethod, reflectionMethod.GetDisplayName());
            }
        }

        private ImmutableArray<GenericParameterValue> GetGenericParameterValues(ImmutableArray<GenericParameterProxy> genericParameters)
        {
            if (genericParameters.IsEmpty)
                return ImmutableArray<GenericParameterValue>.Empty;

            var builder = ImmutableArray.CreateBuilder<GenericParameterValue>(genericParameters.Length);
            foreach (var genericParameter in genericParameters)
            {
                builder.Add(_annotations.GetGenericParameterValue(genericParameter));
            }
            return builder.ToImmutableArray();
        }

        private void ProcessCreateInstanceByName(MethodProxy calledMethod, IReadOnlyList<MultiValue> argumentValues)
        {
            BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
            bool parameterlessConstructor = true;
            int offset = calledMethod.HasImplicitThis() ? 1 : 0;
            if (calledMethod.HasMetadataParametersCount(8) && calledMethod.HasParameterOfType((ParameterIndex)2 + offset, "System.Boolean"))
            {
                parameterlessConstructor = false;
                bindingFlags = BindingFlags.Instance;
                if (argumentValues[3].AsConstInt() is int bindingFlagsInt)
                    bindingFlags |= (BindingFlags)bindingFlagsInt;
                else
                    bindingFlags |= BindingFlags.Public | BindingFlags.NonPublic;
            }

            foreach (var assemblyNameValue in argumentValues[0].AsEnumerable())
            {
                if (assemblyNameValue is KnownStringValue assemblyNameStringValue)
                {
                    if (assemblyNameStringValue.Contents is string assemblyName && assemblyName.Length == 0)
                    {
                        // Throws exception for zero-length assembly name.
                        continue;
                    }
                    foreach (var typeNameValue in argumentValues[1].AsEnumerable())
                    {
                        if (typeNameValue is NullValue)
                        {
                            // Throws exception for null type name.
                            continue;
                        }
                        if (typeNameValue is KnownStringValue typeNameStringValue)
                        {
                            if (!TryResolveTypeNameForCreateInstanceAndMark(calledMethod, assemblyNameStringValue.Contents, typeNameStringValue.Contents, out TypeProxy resolvedType))
                            {
                                // It's not wrong to have a reference to non-existing type - the code may well expect to get an exception in this case
                                // Note that we did find the assembly, so it's not a ILLink config problem, it's either intentional, or wrong versions of assemblies
                                // but ILLink can't know that. In case a user tries to create an array using System.Activator we should simply ignore it, the user
                                // might expect an exception to be thrown.
                                continue;
                            }

                            MarkConstructorsOnType(resolvedType, bindingFlags, parameterlessConstructor ? 0 : null);
                        }
                        else
                        {
                            _diagnosticContext.AddDiagnostic(DiagnosticId.UnrecognizedParameterInMethodCreateInstance, new ParameterProxy(calledMethod, (ParameterIndex)1 + offset).GetDisplayName(), calledMethod.GetDisplayName());
                        }
                    }
                }
                else
                {
                    _diagnosticContext.AddDiagnostic(DiagnosticId.UnrecognizedParameterInMethodCreateInstance, new ParameterProxy(calledMethod, (ParameterIndex)0 + offset).GetDisplayName(), calledMethod.GetDisplayName());
                }
            }
        }

        internal static BindingFlags? GetBindingFlagsFromValue(in MultiValue parameter) => (BindingFlags?)parameter.AsConstInt();

        internal static bool BindingFlagsAreUnsupported(BindingFlags? bindingFlags)
        {
            if (bindingFlags == null)
                return true;

            // Binding flags we understand
            const BindingFlags UnderstoodBindingFlags =
                BindingFlags.DeclaredOnly |
                BindingFlags.Instance |
                BindingFlags.Static |
                BindingFlags.Public |
                BindingFlags.NonPublic |
                BindingFlags.FlattenHierarchy |
                BindingFlags.ExactBinding;

            // Binding flags that don't affect binding outside InvokeMember (that we don't analyze).
            const BindingFlags IgnorableBindingFlags =
                BindingFlags.InvokeMethod |
                BindingFlags.CreateInstance |
                BindingFlags.GetField |
                BindingFlags.SetField |
                BindingFlags.GetProperty |
                BindingFlags.SetProperty;

            BindingFlags flags = bindingFlags.Value;
            return (flags & ~(UnderstoodBindingFlags | IgnorableBindingFlags)) != 0;
        }

        internal static bool HasBindingFlag(BindingFlags? bindingFlags, BindingFlags? search) => bindingFlags != null && (bindingFlags & search) == search;

        internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypesFromBindingFlagsForNestedTypes(BindingFlags? bindingFlags) =>
            (HasBindingFlag(bindingFlags, BindingFlags.Public) ? DynamicallyAccessedMemberTypes.PublicNestedTypes : DynamicallyAccessedMemberTypes.None) |
            (HasBindingFlag(bindingFlags, BindingFlags.NonPublic) ? DynamicallyAccessedMemberTypes.NonPublicNestedTypes : DynamicallyAccessedMemberTypes.None) |
            (BindingFlagsAreUnsupported(bindingFlags) ? DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes : DynamicallyAccessedMemberTypes.None);

        internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypesFromBindingFlagsForConstructors(BindingFlags? bindingFlags) =>
            (HasBindingFlag(bindingFlags, BindingFlags.Public) ? DynamicallyAccessedMemberTypes.PublicConstructors : DynamicallyAccessedMemberTypes.None) |
            (HasBindingFlag(bindingFlags, BindingFlags.NonPublic) ? DynamicallyAccessedMemberTypes.NonPublicConstructors : DynamicallyAccessedMemberTypes.None) |
            (BindingFlagsAreUnsupported(bindingFlags) ? DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors : DynamicallyAccessedMemberTypes.None);

        internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypesFromBindingFlagsForMethods(BindingFlags? bindingFlags) =>
            (HasBindingFlag(bindingFlags, BindingFlags.Public) ? DynamicallyAccessedMemberTypes.PublicMethods : DynamicallyAccessedMemberTypes.None) |
            (HasBindingFlag(bindingFlags, BindingFlags.NonPublic) ? DynamicallyAccessedMemberTypes.NonPublicMethods : DynamicallyAccessedMemberTypes.None) |
            (BindingFlagsAreUnsupported(bindingFlags) ? DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods : DynamicallyAccessedMemberTypes.None);

        internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypesFromBindingFlagsForFields(BindingFlags? bindingFlags) =>
            (HasBindingFlag(bindingFlags, BindingFlags.Public) ? DynamicallyAccessedMemberTypes.PublicFields : DynamicallyAccessedMemberTypes.None) |
            (HasBindingFlag(bindingFlags, BindingFlags.NonPublic) ? DynamicallyAccessedMemberTypes.NonPublicFields : DynamicallyAccessedMemberTypes.None) |
            (BindingFlagsAreUnsupported(bindingFlags) ? DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields : DynamicallyAccessedMemberTypes.None);

        internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypesFromBindingFlagsForProperties(BindingFlags? bindingFlags) =>
            (HasBindingFlag(bindingFlags, BindingFlags.Public) ? DynamicallyAccessedMemberTypes.PublicProperties : DynamicallyAccessedMemberTypes.None) |
            (HasBindingFlag(bindingFlags, BindingFlags.NonPublic) ? DynamicallyAccessedMemberTypes.NonPublicProperties : DynamicallyAccessedMemberTypes.None) |
            (BindingFlagsAreUnsupported(bindingFlags) ? DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties : DynamicallyAccessedMemberTypes.None);

        internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypesFromBindingFlagsForEvents(BindingFlags? bindingFlags) =>
            (HasBindingFlag(bindingFlags, BindingFlags.Public) ? DynamicallyAccessedMemberTypes.PublicEvents : DynamicallyAccessedMemberTypes.None) |
            (HasBindingFlag(bindingFlags, BindingFlags.NonPublic) ? DynamicallyAccessedMemberTypes.NonPublicEvents : DynamicallyAccessedMemberTypes.None) |
            (BindingFlagsAreUnsupported(bindingFlags) ? DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents : DynamicallyAccessedMemberTypes.None);

        internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypesFromBindingFlagsForMembers(BindingFlags? bindingFlags) =>
            GetDynamicallyAccessedMemberTypesFromBindingFlagsForConstructors(bindingFlags) |
            GetDynamicallyAccessedMemberTypesFromBindingFlagsForEvents(bindingFlags) |
            GetDynamicallyAccessedMemberTypesFromBindingFlagsForFields(bindingFlags) |
            GetDynamicallyAccessedMemberTypesFromBindingFlagsForMethods(bindingFlags) |
            GetDynamicallyAccessedMemberTypesFromBindingFlagsForProperties(bindingFlags) |
            GetDynamicallyAccessedMemberTypesFromBindingFlagsForNestedTypes(bindingFlags);

        /// <Summary>
        /// Returns true if the method is a .ctor for System.Type or a type that derives from System.Type (i.e. fields and params of this type can have DynamicallyAccessedMembers annotations)
        /// </Summary>
        private partial bool MethodIsTypeConstructor(MethodProxy method);

        private partial IEnumerable<SystemReflectionMethodBaseValue> GetMethodsOnTypeHierarchy(TypeProxy type, string name, BindingFlags? bindingFlags);

        private partial IEnumerable<SystemTypeValue> GetNestedTypesOnType(TypeProxy type, string name, BindingFlags? bindingFlags);

        private partial bool TryGetBaseType(TypeProxy type, [NotNullWhen(true)] out TypeProxy? baseType);

        private partial bool TryResolveTypeNameForCreateInstanceAndMark(in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType);

        private partial void MarkStaticConstructor(TypeProxy type);

        private partial void MarkEventsOnTypeHierarchy(TypeProxy type, string name, BindingFlags? bindingFlags);

        private partial void MarkFieldsOnTypeHierarchy(TypeProxy type, string name, BindingFlags? bindingFlags);

        private partial void MarkPropertiesOnTypeHierarchy(TypeProxy type, string name, BindingFlags? bindingFlags);

        private partial void MarkPublicParameterlessConstructorOnType(TypeProxy type);

        private partial void MarkConstructorsOnType(TypeProxy type, BindingFlags? bindingFlags, int? parameterCount);

        private partial void MarkMethod(MethodProxy method);

        private partial void MarkType(TypeProxy type);

        private partial bool MarkAssociatedProperty(MethodProxy method);

        // Only used for internal diagnostic purposes (not even for warning messages)
        private partial string GetContainingSymbolDisplayName();
    }
}
